<?php

namespace App\Service\WeChat;


class MiniPayV3
{

    // 提现
    //   https://cloud.tencent.com/developer/article/2233513
    //   https://www.cnblogs.com/leaf-cq/p/14302927.html
    //   小程序 提现 v3 php


    // 小程序支付
    //   独立，不依赖于其他程序


    #######################################################################################

    // 参数
    protected $mini_appid; // 微信小程序 AppID
    protected $mini_key;   // 微信小程序 key
    protected $mchid;      // 微信支付商户号
    protected $serial_no;      // 微信支付商户号


    // 初始化
    public function __construct($appId, $key, $mchid, $serial_no)
    {
        $this->mini_appid = $appId;
        $this->mini_key   = $key;
        $this->mchid      = $mchid;
        $this->serial_no  = $serial_no;
    }

    // 制作小程序支付
    public function makeMiniPay($description, $trade_no, $openid, $money, $notify_url)
    {
        //请求参数(报文主体)
        $apiUrl = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi';   //H5支付

        // 计算过期时间
        $dateTime = new \DateTime();
        $dateTime->setTimestamp(LARAVEL_TIME + 1800);  // 30分钟 后过期
        $dateTime->setTimezone(new \DateTimeZone('+08:00'));   // 设置时区：北京时间
        $expire = $dateTime->format(\DateTime::ATOM);  // 转换为ISO 8601格式   输出: 2018-06-08T10:34:56+08:00


        // 请求报文 与 header
        $body      = [
            'appid'        => $this->mini_appid,
            'mchid'        => $this->mchid,
            'description'  => $description,
            'out_trade_no' => $trade_no,
            'notify_url'   => $notify_url,
            'time_expire'  => $expire,
            'amount'       => ['total' => $money * 100, 'currency' => 'CNY',],
            'payer'        => ['openid' => $openid,],
        ];
        $json      = json_encode($body);
        $headers   = $this->sign('POST', $apiUrl, $json);
        $resultXml = $this->curlPost($apiUrl, $json, $headers);

        // 解析返回的参数
        $result2 = $this->xmlToArray($resultXml);

        return $result2;
    }

    // 退款操作
    public function refund($old_trade_no, $orderNo, $money, $notify_url)
    {
        // 订单退款操作
        $apiUrl = 'https://api.mch.weixin.qq.com/v3/refund/domestic/refunds';//退单接口
        $body   = [
            //'transaction_id' => $transaction_id,   //  原支付交易对应的微信订单号，与out_trade_no二选一
            'out_trade_no'  => $old_trade_no,     //  原支付交易对应的商户订单号，与transaction_id二选一
            'out_refund_no' => $orderNo,  //系统退款单号
            'reason'        => '退款',  //退款原因
            'notify_url'    => $notify_url,   //退款回调
            'amount'        => [
                'refund'   => $money * 100,  //退款金额
                'total'    => $money * 100,  //原订单金额
                'currency' => 'CNY',
            ],
        ];
        $json   = json_encode($body);

        // 制作 header
        $headers = $this->sign('POST', $apiUrl, $json);
        $result  = $this->curlPost($apiUrl, $json, $headers);

        return $result;
    }


    //  微信提现：转账到零钱
    //    这里只能为一个人提现
    public function WechatTransfer($description, $orderNo, $openid, $money, $notify_url)
    {
        $params = [
            'appid'                => $this->mini_appid,
            'mch_id'               => $this->mchid,
            'out_batch_no'         => $orderNo,
            'batch_name'           => '用户提现',
            'batch_remark'         => $description.date('Y-m-d H:i:s'),
            'total_amount'         => $money * 100,
            'total_num'            => 1,  // 只对一个人进行转账
            'notify_url'           => $notify_url,
            // 接收转账的人的信息
            'transfer_detail_list' => [
                [
                    //单次转账一人，多人添加数组即可
                    'out_detail_no'   => $orderNo.'_1',  // 第一个人
                    'transfer_remark' => $description,
                    'transfer_amount' => $money * 100,
                    'openid'          => $openid,
                ],
            ],
        ];

        ksort($params);
        reset($params);


        $apiUrl = 'https://api.mch.weixin.qq.com/v3/transfer/batches';//微信提现接口
        $json   = json_encode($params);
        //$token  = $this->getToken('POST', $apiUrl, $json); //获取header Token认证

        // 制作 header
        $headers = $this->sign('POST', $apiUrl, $json);
        $result  = $this->curlPost($apiUrl, $json, $headers);

        return $result;
    }


    // 组合给前端调取支付： 注意参数大小写
    public function toJsPay($appId, $timeStamp, $prepayid)
    {
        $noceStr = $this->noceStr($timeStamp);
        $pay     = ["timeStamp" => $timeStamp, "nonceStr" => $noceStr, "package" => "prepay_id=".$prepayid, "signType" => "RSA",];

        //构造签名串
        $message = $appId."\n".
                   $timeStamp."\n".
                   $noceStr."\n".
                   "prepay_id=".$prepayid."\n";//报文主体

        //计算签名值
        $pay["paySign"] = $this->sslSign($message);

        return $pay;
    }


    /**
     * 签名
     * @param string $http_method 请求方式GET|POST
     * @param string $url url
     * @param string $body 报文主体
     * @return array
     */
    public function sign($http_method = 'POST', $url = '', $body = '')
    {
        $timestamp = time();   //时间戳
        $nonce     = $this->noceStr($timestamp);  //随机串
        $url_parts = parse_url($url);
        $_url      = ($url_parts['path'].(!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));   // 组合标准的url

        //构造签名串
        $message = $http_method."\n".
                   $_url."\n".
                   $timestamp."\n".
                   $nonce."\n".
                   $body."\n";//报文主体

        //计算签名值
        // $pri_key = $this->getMchKey();  //私钥
        //openssl_sign($message, $raw_sign, $pri_key, 'sha256WithRSAEncryption');
        //$sign = base64_encode($raw_sign);

        //计算签名值
        $sign = $this->sslSign($message);

        // 计算token
        $token = sprintf(
            'WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $this->mchid,
            $nonce,
            $timestamp,
            $this->serial_no,
            $sign
        );

        // 设置HTTP头
        $headers = [
            'Accept: application/json',
            'User-Agent: */*',
            'Content-Type: application/json; charset=utf-8',
            'Authorization: '.$token,
            //'User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
        ];

        return $headers;
    }


    // 根据证书文件 加密
    protected function sslSign($message)
    {
        $pri_key = $this->getMchKey();  //私钥
        //计算签名值
        openssl_sign($message, $raw_sign, $pri_key, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);

        return $sign;
    }

    // 根据证书文件 加密
    private function getEncrypt($message)
    {
        $public_key_path = '平台证书路径';
        $public_key      = file_get_contents($public_key_path);
        $encrypted       = '';
        if(openssl_public_encrypt($message, $encrypted, $public_key, OPENSSL_PKCS1_OAEP_PADDING)){
            //base64编码
            $sign = base64_encode($encrypted);
        } else{
            throw new Exception('encrypt failed');
        }

        return $sign;
    }

    // 获取token
    protected function getToken($http_method, $url, $body = '')
    {
        // $url = 'https://api.mch.weixin.qq.com/v3/certificates';


        //$serial_no = $this->merchantSerialNo();
        //$pri_key   = $this->getMchKey2();

        $pri_key   = $this->getMchKey();  //私钥
        $serial_no = $this->serial_no;

        $timestamp = time();   //时间戳
        $url_parts = parse_url($url);//获取请求的绝对URL
        $nonce     = $timestamp.rand('10000', '99999');//请求随机串
        $_url      = ($url_parts['path'].(!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        $message   = $http_method."\n".
                     $_url."\n".
                     $timestamp."\n".
                     $nonce."\n".
                     $body."\n";

        openssl_sign($message, $raw_sign, $pri_key, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);//签名


        $schema = 'WECHATPAY2-SHA256-RSA2048';
        $token  = sprintf(
            $schema.' mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $this->mchid,
            $nonce,
            $timestamp,
            $serial_no,
            $sign
        ); //微信返回token

        return $token;
    }

    // 从证书中获取商户序列号 serial_no
    protected function merchantSerialNo()
    {
        // $client_cert_path = $this->cert_pem;  //商户证书
        // $client_key_path  = $this->key_pem;   //商户密匙
        $client_cert_path = '';  //商户证书
        $stream_opts      = ["ssl" => ["verify_peer" => false, "verify_peer_name" => false,],];
        $client_cert_arr  = openssl_x509_parse(file_get_contents($client_cert_path, false, stream_context_create($stream_opts)));
        $serial_no        = $client_cert_arr['serialNumberHex'];//证书序列号(忽略)

        return $serial_no;
    }

    //post请求
    public function curlPost($url, $data, $headers = array())
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        //设置header头
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        // POST数据
        curl_setopt($ch, CURLOPT_POST, 1);
        // 把post的变量加上
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        $output = curl_exec($ch);
        curl_close($ch);

        return $output;
    }

    //get请求
    public function curlGet($url, $headers = array())
    {
        $info = curl_init();
        curl_setopt($info, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($info, CURLOPT_HEADER, 0);
        curl_setopt($info, CURLOPT_NOBODY, 0);
        curl_setopt($info, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($info, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($info, CURLOPT_SSL_VERIFYHOST, false);
        //设置header头
        curl_setopt($info, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($info, CURLOPT_URL, $url);
        $output = curl_exec($info);
        curl_close($info);

        return $output;
    }


    // xml转为数组
    protected function xmlToArray($xmlStr)
    {
        $postStr = $xmlStr;
        $msg     = (array)simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);

        return $msg;
    }


    // 私钥，这里需要手动防止好私钥证书，并且需要修改下存放的位置
    protected function getMchKey()
    {
        //path->私钥文件存放路径
        return openssl_get_privatekey(file_get_contents('path'));
    }

    // 私钥，这里需要手动防止好私钥证书，并且需要修改下存放的位置
    protected function getMchKey2()
    {
        //path->私钥文件存放路径
        //$client_key_path  = $this->key_pem;  //商户密匙
        $client_key_path = '';  //商户密匙
        $stream_opts     = ["ssl" => ["verify_peer" => false, "verify_peer_name" => false,],];
        $mch_private_key = file_get_contents($client_key_path, false, stream_context_create($stream_opts));//密钥

        return $mch_private_key;
    }


    // 获取随机字符串
    protected function noceStr($timeStamp)
    {
        return md5(rand(100, 1000).$timeStamp);
    }

    //  获得随机字符串
    protected function getRandomStr($len, $special = false)
    {
        $chars = array(
            "a",
            "b",
            "c",
            "d",
            "e",
            "f",
            "g",
            "h",
            "i",
            "j",
            "k",
            "l",
            "m",
            "n",
            "o",
            "p",
            "q",
            "r",
            "s",
            "t",
            "u",
            "v",
            "w",
            "x",
            "y",
            "z",
            "A",
            "B",
            "C",
            "D",
            "E",
            "F",
            "G",
            "H",
            "I",
            "J",
            "K",
            "L",
            "M",
            "N",
            "O",
            "P",
            "Q",
            "R",
            "S",
            "T",
            "U",
            "V",
            "W",
            "X",
            "Y",
            "Z",
            "0",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
        );

        if($special){
            $chars = array_merge($chars, array(
                "!",
                "@",
                "#",
                "$",
                "?",
                "|",
                "{",
                "/",
                ":",
                ";",
                "%",
                "^",
                "&",
                "*",
                "(",
                ")",
                "-",
                "_",
                "[",
                "]",
                "}",
                "<",
                ">",
                "~",
                "+",
                "=",
                ",",
                ".",
            ));
        }

        $charsLen = count($chars) - 1;
        shuffle($chars);                            //打乱数组顺序
        $str = '';
        for($i = 0; $i < $len; $i++){
            $str .= $chars[mt_rand(0, $charsLen)];    //随机取出一位
        }

        return $str;
    }


}


